
/***************************************************************************** \
 *                              ColUtilsOpenGL
\*****************************************************************************/

/*! @file
 *
 *  @brief
 *    Util-functions to help programm with OpenGL
 *
 *  @author Weiyu Yi, wyi@tu-clausthal.de
 *
 */

//---------------------------------------------------------------------------
//  Includes
//---------------------------------------------------------------------------

#include <string>
#include <stdlib.h>

#define COL_EXPORT

#include <col_import_export.h>
#include <ColUtilsOpenGL.h>

namespace col
{

/** 8 Vertices of standard cube
 */
Point3 Cube::points[8] = { 
		Point3(-1,-1, 1),
		Point3( 1,-1, 1),
		Point3( 1, 1, 1),
		Point3(-1, 1, 1),
		Point3(-1,-1,-1),
		Point3( 1,-1,-1),
		Point3( 1, 1,-1),
		Point3(-1, 1,-1)
};

/** 6 Quads of standard cube
 */
unsigned int Cube::quat_indices[6*4]={
		0,1,2,3,
		1,5,6,2,
		2,6,7,3,
		0,3,7,4,
		0,4,5,1,
		4,7,6,5
};

/** 12 Triangles of standard cube
 */
unsigned int Cube::tri_indices[12*3]={
		0,1,2,
		2,3,0,
		1,5,6,
		6,2,1,
		2,6,7,
		7,3,2,
		0,3,7,
		7,4,0,
		0,4,5,
		5,1,0,
		4,7,6,
		6,5,4
};

REAL Icosahedron::x=0.525731112119133606;
REAL Icosahedron::z=0.850650808352039932;

/** 12 Vertices of standard icosahedron
 */
Point3 Icosahedron::points[12] = {
		Point3(	 -x, 0.0,   z ),
		Point3(   x, 0.0,   z ),
		Point3(  -x, 0.0,  -z ),
		Point3(   x, 0.0,  -z ),
		Point3( 0.0,   z,   x ),
		Point3( 0.0,   z,  -x ),
		Point3( 0.0,  -z,   x ),
		Point3( 0.0,  -z,  -x ),
		Point3(   z,   x, 0.0 ),
		Point3(	 -z,   x, 0.0 ),
		Point3(   z,  -x, 0.0 ),
		Point3(  -z,  -x, 0.0 )
};

/** 20 Triangles of standard icosahedron
 */
unsigned int Icosahedron::indices[20*3] = {
		 1, 4, 0,
		 4, 9, 0,
		 4, 5, 9,
		 8, 5, 4,
		 1, 8, 4,
		 1,10, 8,
		10, 3, 8,
		 8, 3, 5,
		 3, 2, 5,
		 3, 7, 2,
		 3,10, 7,
		10, 6, 7,
		 6,11, 7,
		 6, 0,11,
		 6, 1, 0,
		10, 1, 6,
		11, 0, 9,
		 2,11, 9,
		 5, 2, 9,
		11, 2, 7
};

	
const REAL Trackball::HALF_PI = 1.570796326794897;
const REAL Trackball::ROTATE_SCALE = 2.0;
const REAL Trackball::ZOOM_SCALE = 5.0;

/** default constructor of class Trackball
 */
Trackball::Trackball()
	:
	track_mode( TRACK_STOP )
{
}

/** project the 2D position to the surface of 3D trackball
 */
Vector3 Trackball::pToV( int x, int y, int width, int height )
{
	double d;
	Vector3 v;

	v[0] = ( 2.0*x - width ) / width;
	v[1] = ( height - 2.0*y )/ height;
	v[2] = 0.0;

	d = length( v );
	d = (d<1.0) ? d : 1.0;

	v[2] = sqrt( 1.001 - d*d );

	return col::normalize( v );
}

/** get the current zooming scale made to the trackball
 */
void Trackball::getZoom( REAL &zm ) const
{
	zm = zoom * ZOOM_SCALE;
}

/** get the rotation which is made to the trackball
 */
void Trackball::getRotation( REAL ax[3], REAL & al ) const
{
	// almost no movement
	if ( angle < col::NearZero )
	{
		al = 0;
		Vector3 nv = col::normalize( Vector3( 1.0, 1.0, 1.0 ) );
		ax[0] = nv[0];
		ax[1] = nv[1];
		ax[2] = nv[2];
	}
	else
	{
		al = angle * ROTATE_SCALE;
		ax[0] = axis[0];
		ax[1] = axis[1];
		ax[2] = axis[2];
	}
}

/** give the viewport to the trackball
 */
void Trackball::setViewport( int w, int h )
{
	width = w;
	height = h;
}

/**	get the current track mode of the trackball
 */
trackModeE Trackball::getTrackMode() const
{
	return track_mode;
}

/** define the action of trackball about mouseclick
 */
void Trackball::mouseClick( int button, int state, int x, int y )
{
	if ( state == GLUT_UP )
	{
		track_mode = TRACK_STOP;
		return;
	}
	else
	if ( state == GLUT_DOWN )
	{
		if ( button == GLUT_LEFT_BUTTON ) track_mode = TRACK_ROTATE;
		if ( button == GLUT_RIGHT_BUTTON ) track_mode = TRACK_ZOOM;

		// 
		lastPos = pToV( x, y, width, height );
		return;
	}
}

/** define the action of trackball about moving mouse
 */
void Trackball::mouseMoving( int x, int y )
{
	Vector3 curPos = pToV( x, y, width, height );

	// compute theta and cross product
	Vector3 diff = curPos - lastPos;
	REAL velocity = col::length( diff );

	//if ( velocity < 0.01 )
		//return;

	angle = /*HALF_PI **/ velocity;
	zoom = diff[0];
	Vector3 v = col::cross( lastPos, curPos );
	axis = col::normalize( v );

	// update position
	lastPos = curPos;
}

/** print the geometry to a file with OBJ format
 */
int printOBJ( const ColGeometry * geo, const char * file_name )
{
	FILE *fp;
	if ( file_name == NULL ) 
		fp = stdout;
	{
		fp = fopen( file_name, "w" );
		if ( fp == NULL ) 
			return -1;
	}

	Pnt3Array::const_iterator iter = geo->getPointsPtr()->begin();
	for ( ; iter != geo->getPointsPtr()->end(); ++iter )
	{   
		REAL p0 = (*iter)[0];
		REAL p1 = (*iter)[1];
		REAL p2 = (*iter)[2];
		fprintf( fp, "v %f %f %f\n", p0, p1, p2 );
		//fprintf( fp, "v %f %f %f\n", (*iter)[0], (*iter)[1], (*iter)[2] );
	}
	fprintf( fp, "# %d vertices\n\n", geo->getPointsPtr()->size() );

	std::vector<Primitive>::const_iterator iter_prim = geo->getPrimitivesPtr()->begin();
	for ( ; iter_prim != geo->getPrimitivesPtr()->end(); ++iter_prim )
	{
		fprintf( fp, "f" );
		for( unsigned int k=0; k < iter_prim->size(); ++k )
		{
			fprintf( fp, " %d",  (*iter_prim)[k]+1 );
		}
		fprintf( fp, "\n" );
	}
	fprintf( fp, "# %d faces\n", geo->getPrimitivesPtr()->size() );

	fclose( fp );
	return 0;
}

/** load the geometry from a OBJ file
 */
int loadGeometryFromFile( ColGeometry * geo, const char *file_name  )
{
	if ( file_name == NULL ) 
		return -1;	

	std::ifstream fs( file_name );
	if ( fs == NULL ) 
		return -1;

	bool same_prim = true;
	unsigned int prim_size = 0;

	std::string sline;
	while( std::getline( fs, sline ) )
	{
		if ( sline.size() < 2 )
			continue;

		if ( sline[0] == 'v' && sline[1] == ' ' )
		{
			// vertex;
			std::size_t mark = 2;
			std::size_t found = 1;
			found = sline.find_first_of( " ", found+1 );

			Point3 p;
			unsigned int k=0;
			while (  found < std::string::npos  )
			{
				std::string value = sline.substr( mark, found-mark );
				mark = found+1;
				found = sline.find_first_of( " ", found+1 );

				REAL v = atof( value.c_str() );
				p[k++] = v;
			}
			std::string value = sline.substr( mark, std::string::npos-mark );
			REAL v = atof( value.c_str() );
			p[k++] = v;
			geo->editPointsPtr()->push_back( p );

			continue;
		}
		
		if ( sline[0] == 'f' && sline[1] == ' ' )
		{
			// primitive
			std::size_t mark = 2;
			std::size_t found = 1;
			found = sline.find_first_of( " ", found+1 );

			Primitive prim;
			while (  found < std::string::npos  )
			{
				std::string value = sline.substr( mark, found-mark );
				mark = found+1;
				found = sline.find_first_of( " ", found+1 );

				// value = [v]/[vn]/[vt]
				std::size_t new_found = value.find_first_of( "/" );
				std::string new_value = value.substr( 0, new_found-0 );
				unsigned int i = atoi( new_value.c_str() );
				
				// the index begins with 1 in OBJ file
				prim.push_back( i-1 );
			}
			std::string value = sline.substr( mark, std::string::npos-mark );
			// value = [v]/[vn]/[vt]
			std::size_t new_found = value.find_first_of( "/" );
			std::string new_value = value.substr( 0, new_found-0 );
			unsigned int i = atoi( new_value.c_str() );
				
			// the index begins with 1 in OBJ file
			prim.push_back( i-1 );

			if ( prim_size == 0 ) prim_size = prim.size();
			else if ( prim_size != prim.size() ) same_prim = false;

			geo->editPrimitivesPtr()->push_back( prim );

			continue;
		}
	}

	geo->setPrimType( ANY );
	if ( same_prim == true )
	{
		if ( prim_size == 3 )
			geo->setPrimType( TRIANGLES );
		if ( prim_size == 4 )
			geo->setPrimType( QUADS );
	}

	geo->updateBBox();

	return 0;
}

/** a wrapper function for the OpenGL geometry-draw expressions
 */
void drawGeometry( const ColGeometry * geo,
				   GLenum mode,
				   const GLdouble * color )
{
	glColor3dv( color );

	// mode = GL_FILL | GL_LINE | GL_POINT
	glPolygonMode ( GL_FRONT_AND_BACK, mode );

	for ( unsigned int i=0; i<geo->getPrimitives().size(); i++ )
	{
		glBegin( GL_POLYGON );
		{
			for ( unsigned int j=0; j<geo->getPrimitives()[i].size(); j++ )
			{
				REAL p0 = geo->getPoints()[ geo->getPrimitives()[i][j] ][0];
				REAL p1 = geo->getPoints()[ geo->getPrimitives()[i][j] ][1];
				REAL p2 = geo->getPoints()[ geo->getPrimitives()[i][j] ][2];
				glVertex3d( p0, p1, p2 );
			}	
		}
		glEnd();
	}
}

/**  Create a polyhedron from simple vertex and face arrays
 *
 * @param vertex		vertex array (in)
 * @param face			faces array	(in, could get sorted)
 * @param gl_type		= GL_LINE_LOOP, GL_POLYGON, etc...
 * @param skip_redundant	skip faces with <3 vertices, if true
 * @param normals		normals array (can be NULL)
 *
 * @return
 *   The node.
 *
 * Create a polyhedron which is the object described by the planes
 * given by normals, nfaces, vertex, face, face_nv.
 * Actually, normals is only used for sorting the vertices of the faces.
 * If @a normals == NULL, then we assume that vertices in @a face[] are
 * already sorted in counter-clockwise order.
 * @a Face contains indices into vertex.
 *
 * There may be redundant faces, i.e., faces with face[i].size() = 0,1,2.
 * The geometry will have no material.
 *
 * No material is assigned.
 *
 * @warning
 *   If @a normals != NULL, then @a face @e will get sorted!
 *
 * @throw XColBug
 *   If there are no faces with any vertices.
 *
 * @pre
 *   - Planes are given by Ori*x - d = 0.
 *   - @a normals has @a face.size() many normals.
 *
 **/
void buildGeometryFromPoints( ColGeometry * geo,
							  const Pnt3Array &vertex,
							  vector<TopoFace> &face )
{
	geo->clearOldData();

	// points
	geo->editPointsPtr()->resize( vertex.size() );
	for ( unsigned int i = 0; i < vertex.size(); i ++ )
		(*geo->editPointsPtr())[i] = vertex[i];

	// geometry/primitives
	for ( unsigned int i = 0; i < face.size(); i ++ )
	{
		// polygon
		Primitive prim( face[i].size() );
		for ( unsigned int j = 0; j < face[i].size(); j ++ )
		{
			if ( face[i][j] >= vertex.size() )
				throw XCollision("buildGeometryFromPoints: index face[%d][%d] = %d >= "
						 "num. vertices = %d", i,j, face[i][j], vertex.size() );

			prim[j] = ( face[i][j] );
		}

		geo->editPrimitivesPtr()->push_back( prim );
	}
}


/**  @overload
 *
 * @param vertex,nvertices		vertex array (in)
 * @param face,face_nv,nfaces	faces array	(in, could get sorted)
 * @param gl_type		= GL_LINE_LOOP, GL_POLYGON, etc...
 * @param skip_redundant	skip faces with <3 vertices, if true
 * @param normals		normals array (can be NULL)
 *
 * @a Face_nv[i] contains the number of vertices of @a face[i].
 * @a Nfaces contains the number of faces in @a face.
 * There may be redundant faces, i.e., faces with face_nv[i] = 0,1,2.
 *
 * @throw XColBug
 *  If a face has more than NumOri many vertices.
 *
 * @pre
 *   - @a normals has @a nfaces many normals.
 *   - @a face[i] @e must have @a Dop::NumOri many columns!
 *
 * @todo
 *   - immer noch wird die Variable NumOri gebraucht..
 **/
void buildGeometryFromPoints( ColGeometry * geo, 
							  const Point3 vertex[], unsigned int nvertices,
							  unsigned int face[],
							  const unsigned int face_nv[],
							  unsigned int nfaces )
{
	geo->clearOldData();

	for ( unsigned int i = 0; i < nfaces; i ++ )
		if ( face_nv[i] > Dop::NumOri )
			throw XCollision("geomFromPoints: a face has more than "
						   "NumOri vertices");

	vector<TopoFace> face_vec( nfaces );

    int offset = 0;
    face_vec[0].set(face,face_nv[0]);
    for ( unsigned int i = 1; i < nfaces; i ++ )
    {    	
        offset = offset + face_nv[i-1];
		face_vec[i].set( face+offset, face_nv[i] );
    }
     
	Pnt3Array vertex_vec( nvertices );
	for ( unsigned int i = 0; i < nvertices; i ++ )
		vertex_vec[i] = vertex[i];

	buildGeometryFromPoints( geo, vertex_vec, face_vec );
}
	
/** the intermediate function called recursively by makeSphere function 
 */
void addPoint( Point3 pt, unsigned int i, REAL radius, Pnt3Array *points, Vec3Array *norms )
{
	Vector3 v3( pt[0], pt[1], pt[2] );
	Vector3 norm = normalize( v3 );
	norms->push_back( norm );
	
	Point3 p( norm * radius );
	points->push_back( p );
}

/** the intermediate function called recursively by makeSphere function 
 */
void subdivideTriangle( unsigned int i1,
		   			    unsigned int i2,
						unsigned int i3,
						unsigned int depth,
						REAL radius,
						unsigned int *len,
						Pnt3Array *points, 
						Vec3Array *norms,
						std::vector<Primitive> *tris )
{
	if ( depth == 0 )
	{
		Primitive tri;
		tri.push_back( i1 );
		tri.push_back( i2 );
		tri.push_back( i3 );
		tris->push_back( tri );

		return;
	}

	Point3 v1 = (*points)[i1];
	Point3 v2 = (*points)[i2];
	Point3 v3 = (*points)[i3];
	Point3 v12, v23, v31;

	v12 = v1 + (v2 - v1) * .5f;
	v23 = v2 + (v3 - v2) * .5f;
	v31 = v3 + (v1 - v3) * .5f;

	unsigned i12 = (*len)++;
	unsigned i23 = (*len)++;
	unsigned i31 = (*len)++;

	addPoint( v12, i12, radius, points, norms );
	addPoint( v23, i23, radius, points, norms );
	addPoint( v31, i31, radius, points, norms );

	subdivideTriangle( i1, i12, i31, depth-1, radius, len, points, norms, tris );
	subdivideTriangle( i2, i23, i12, depth-1, radius, len, points, norms, tris );
	subdivideTriangle( i3, i31, i23, depth-1, radius, len, points, norms, tris );
	subdivideTriangle(i12, i23, i31, depth-1, radius, len, points, norms, tris );
}

/** make a sphere geometry
 *
 * @param ColGeometry*
 * @param unsigned int  recursive count, when 0, is this sphere an
 *        icosahedron
 * @param REAL  radius of the sphere
 */
void makeSphere( ColGeometry * geo, unsigned int depth, REAL radius )
{
	geo->clearOldData();

	const REAL HALF_PI = 1.570796326794897;
	
	// rotate..
	// in OpenSG: Quaternion q(Vec3f(0,1,0), osgacos(Z) + HalfPi);
	// why is it necessary?
	//Matrix4 mat = Matrix4::rotationY( acos( Icosahedron::z ) + HALF_PI );
	Matrix4 mat = Matrix4::rotation( acos( Icosahedron::z ) + HALF_PI, Vector3(0.0,1.0,0.0) );

	// initiate the sphere from icosahedron
	Pnt3Array points;
	Vec3Array norms;
	for ( unsigned int i=0; i<12; i++ )
	{
		//Point3 p3( Icosahedron::points[i*3+0], Icosahedron::points[i*3+1], Icosahedron::points[i*3+2] );
		Point3 p3( Icosahedron::points[i] );
		Vector4 v4 = mat * p3;
		Vector3 v3( v4[0], v4[1], v4[2] );

		// Care! normalize( v3 ) =!= normalize( v4 )
		Vector3 norm = normalize( v3 );
		norms.push_back( norm );

		Vector3 vnr = norm * radius;
		Point3 pt( vnr[0], vnr[1], vnr[2] );
		points.push_back( pt );
	}

	std::vector<Primitive> tris;
	// recursive subdivide
	unsigned int len = 12;
	for ( unsigned int i=0; i<20; i++ )
	{
		subdivideTriangle( Icosahedron::indices[ i*3+0 ], Icosahedron::indices[ i*3+1 ], Icosahedron::indices[ i*3+2 ], depth, radius, &len, &points, &norms, &tris );
	}

	*( geo->editPointsPtr() ) = points;
	*( geo->editPrimitivesPtr() ) = tris;

	geo->updateBBox();
}

/** make a cube geometry
 *
 * @param ColGeometry*  the pointer to the object of type ColGeometry
 * @param REAL  the half length of the edge of the cube
 * @param bool  primitives are quats if it's set to true, otherwise
 *        triangles 
 */
void makeCube( ColGeometry * geo, REAL radius, bool quat )
{
	geo->clearOldData();

	Point3 pcube[8];
	for ( unsigned int i=0; i<8; i++ )
	{
		pcube[i] = Point3( Cube::points[i][0] * radius, 
						   Cube::points[i][1] * radius, 
						   Cube::points[i][2] * radius );
	}

	if ( quat == true )
	{
		unsigned int face_nv[6] = { 4, 4, 4, 4, 4, 4 };
		buildGeometryFromPoints( geo, pcube, 8, Cube::quat_indices, face_nv, 6 );
	}
	else
	{
		unsigned int face_nv[12] = { 3,3, 3,3, 3,3, 3,3, 3,3, 3,3 };
		buildGeometryFromPoints( geo, pcube, 8, Cube::tri_indices, face_nv, 12 );
	}

	geo->updateBBox();
}

/** make a plane triangle mesh
 * 
 * @param geo   the pointer of the geometry
 * @param xsize the horizontal length of x-axis
 * @param ysize the vertical length of y-axis
 * @param hor   the horizontal grid size
 * @param vert  the vertical grid size
 */
void makePlane( ColGeometry * geo, REAL xsize, REAL ysize, unsigned int hor, unsigned int vert )
{
	if ( !vert || !hor )
	{
		fprintf( stderr, "%s : %s\n", __FILE__, __FUNCTION__ );
		fprintf( stderr, "vert and hor must be greater than 0\n" );
		fprintf( stderr, "now vert = %d, hor = %d\n", vert, hor );
		return;
	}
	geo->clearOldData();

	REAL xstep=xsize / hor;
	REAL ystep=ysize / vert;

	// points
	for ( unsigned int y=0; y <= hor;  y++ )
	for ( unsigned int x=0; x <= vert; x++ )
	{
		geo->editPointsPtr()->push_back( Point3( x * xstep - xsize / 2, y * ystep - ysize / 2, 0 ) );
	}
	// primitives
	for ( unsigned int y=0; y < hor;  y++ )
	for ( unsigned int x=0; x < vert; x++ )
	{
		Primitive p1(3);
		p1[0] = x + (y  )*(vert+1);
		p1[1] = x + (y  )*(vert+1) + 1;
		p1[2] = x + (y+1)*(vert+1) + 1;
		geo->editPrimitivesPtr()->push_back( p1 );

		Primitive p2(3);
		p2[0] = x + (y  )*(vert+1);
		p2[1] = x + (y+1)*(vert+1) + 1;
		p2[2] = x + (y+1)*(vert+1);
		geo->editPrimitivesPtr()->push_back( p2 );
	}
	
	// type
	geo->setPrimType( col::TRIANGLES );

	geo->updateBBox();
}

}//namespace
